import numpy as np
import pandas as pd
from typing import Tuple, Optional, Dict
from sklearn.preprocessing import StandardScaler, MinMaxScaler

class DataPreprocessor:
    def __init__(self):
        self.scalers: Dict[str, StandardScaler] = {}
        
    def handle_missing_values(self, df: pd.DataFrame,
                             method: str = 'interpolate') -> pd.DataFrame:
        """Handle missing values in the dataset
        
        Args:
            df (pd.DataFrame): Input dataframe
            method (str): Method to handle missing values
                         ('interpolate', 'forward', 'backward', 'mean')
                         
        Returns:
            pd.DataFrame: Dataframe with handled missing values
        """
        if method == 'interpolate':
            return df.interpolate(method='time')
        elif method == 'forward':
            return df.fillna(method='ffill')
        elif method == 'backward':
            return df.fillna(method='bfill')
        elif method == 'mean':
            return df.fillna(df.mean())
        else:
            raise ValueError(f"Unknown method: {method}")
    
    def remove_outliers(self, df: pd.DataFrame,
                       method: str = 'iqr',
                       threshold: float = 1.5) -> pd.DataFrame:
        """Remove outliers from the dataset
        
        Args:
            df (pd.DataFrame): Input dataframe
            method (str): Method to detect outliers ('iqr', 'zscore')
            threshold (float): Threshold for outlier detection
            
        Returns:
            pd.DataFrame: Dataframe with outliers removed
        """
        if method == 'iqr':
            Q1 = df.quantile(0.25)
            Q3 = df.quantile(0.75)
            IQR = Q3 - Q1
            mask = ~((df < (Q1 - threshold * IQR)) | (df > (Q3 + threshold * IQR)))
            return df[mask]
        elif method == 'zscore':
            z_scores = np.abs((df - df.mean()) / df.std())
            mask = z_scores < threshold
            return df[mask]
        else:
            raise ValueError(f"Unknown method: {method}")
    
    def scale_data(self, data: np.ndarray,
                   feature_name: str,
                   method: str = 'standard') -> np.ndarray:
        """Scale the data using specified method
        
        Args:
            data (np.ndarray): Input data
            feature_name (str): Name of the feature being scaled
            method (str): Scaling method ('standard', 'minmax')
            
        Returns:
            np.ndarray: Scaled data
        """
        if method == 'standard':
            if feature_name not in self.scalers:
                self.scalers[feature_name] = StandardScaler()
                return self.scalers[feature_name].fit_transform(data.reshape(-1, 1))
            return self.scalers[feature_name].transform(data.reshape(-1, 1))
        elif method == 'minmax':
            if feature_name not in self.scalers:
                self.scalers[feature_name] = MinMaxScaler()
                return self.scalers[feature_name].fit_transform(data.reshape(-1, 1))
            return self.scalers[feature_name].transform(data.reshape(-1, 1))
        else:
            raise ValueError(f"Unknown method: {method}")
    
    def inverse_scale(self, data: np.ndarray,
                      feature_name: str) -> np.ndarray:
        """Inverse transform scaled data
        
        Args:
            data (np.ndarray): Scaled data
            feature_name (str): Name of the feature
            
        Returns:
            np.ndarray: Original scale data
        """
        if feature_name not in self.scalers:
            raise ValueError(f"No scaler found for feature: {feature_name}")
        return self.scalers[feature_name].inverse_transform(data.reshape(-1, 1))